package edu.utexas.esel.jgraphpad.archpadplugin.action;

import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphLayoutCache;

import com.jgraph.JGraphEditor;
import com.jgraph.editor.JGraphEditorAction;
import com.jgraph.editor.JGraphEditorResources;
import com.jgraph.pad.action.JGraphpadFormatAction;
import com.jgraph.pad.dialog.JGraphpadDialogs;

import edu.utexas.esel.jgraphpad.archpadplugin.ArchPadPlugin;
import edu.utexas.esel.jgraphpad.archpadplugin.Utils;
import edu.utexas.esel.jgraphpad.archpadplugin.graph.ElementGraphCell;
import edu.utexas.esel.jgraphpad.archpadplugin.model.ArchPadDiagram;
import edu.utexas.esel.jgraphpad.archpadplugin.model.ElementTreeNode;
import edu.utexas.esel.jgraphpad.archpadplugin.model.RegionManager;

/**
 * ArchPad actions used to manipulate cells in the graph representing ArchPad
 * model. Such actions are collapsing a set of elements into a sub-architecture,
 * expanding a sub-architecture, and so on.
 *
 * @author svetoslavganov@mail.utexas.edu
 *
 */
public class ArchPadCellAction extends JGraphpadFormatAction {	

	private static final long serialVersionUID = 1L;

	/**
	 * Specifies the name for grouping the elements into a composition.
	 */
    public static final String NAME_COMPOSE_COMPOSITION = "composeComposition";

	/**
	 * Specifies the name for ungrouping the elements of a composition.
	 */
    public static final String NAME_DECOMPOSE_COMPOSITION =
    	"decomposeComposition";

    /**
     * Message show in case of unacceptable region name.
     */
    public static final String MESSAGE_UNACCEPTSABLE_REGION_NAME =
    	"Unacceptable region name!!! Region name is either empty or a "
    	+ "duplicate.";

    /**
	 * Specifies the name for creating a region from the selected elements.
	 */
    public static final String NAME_CREATE_REGION = "createRegion";

	private static final String PROVIDE_REGION_NAME = "provideRegionName";

	private static final String REGION_DEFAULT_NAME = "Region";

	/**
	 * Constructs a new format action for the specified name. If the action name
	 * starts with <code>toggle</code> or <code>switch</code> then the
	 * action is configured to be a toggle action.
	 * 
	 * @param name The name of the action to be created.
	 */
	public ArchPadCellAction(String name) {
		super(name);
	}

	public void actionPerformed(ActionEvent event) {
		JGraph graph = getPermanentFocusOwnerGraph();
		if (graph != null) {
	        String name = getName();
		    if (NAME_COMPOSE_COMPOSITION.equals(name)) {
			    doGroupAsComposition(graph,
			    	getValue(ArchPadPlugin.KEY_COMPOSITION_PROTOTYPE));
		    } else if (NAME_DECOMPOSE_COMPOSITION.equals(name)) {
		    	doUngroupComposition(graph);
		    } else if (NAME_CREATE_REGION.equals(name)) {
		    	createRegion(graph);
		    }
		}
	}

	/**
	 * Sets the parent id attribute in the roots of the elements stored in the
	 * children cells.
	 *
	 * @param parentId The id to set.
	 * @param group The parent whose id to to set to children.
	 * @param children The children whose parent id attribute to set.
	 */
	private void setParentAttribute(String parentId, Object[] children) {
		for (Object object : children) {
			if (object instanceof ElementGraphCell) {
			    ElementGraphCell child = (ElementGraphCell) object;
			    Utils.setElementRootAttribute(child,
				    ElementTreeNode.ATTRIBUTE_PARENT_ID, parentId);
			}
		}
	}

	/**
	 * Creates a new composition by grouping the selected elements.
	 *
	 * @param graph The graph to perform the operation in.
	 * @param prototype The group prototype.
	 */
	private void doGroupAsComposition(JGraph graph, Object prototype) {
		if (prototype != null) {
			// Reorders the selection according to the layering
			Object[] cells = graph.order(graph.getSelectionCells());
			if (cells != null && cells.length > 1) {
				// Gets a clone of the prototype group cell
				ElementGraphCell group = (ElementGraphCell) DefaultGraphModel
				    .cloneCell(graph.getModel(), prototype);

				Rectangle2D selection = graph.getCellBounds(cells);
				GraphConstants.setBounds(group.getAttributes(),
					new Rectangle2D.Double(selection.getCenterX(),
					selection.getCenterY(), ArchPadPlugin.DEFAULT_VERTEX_SIDE,
					ArchPadPlugin.DEFAULT_VERTEX_SIDE));

				String parentId = group.getAttribute(
					ElementGraphCell.ATTRIBUTE_ID);
				setParentAttribute(parentId, cells);

				graph.getGraphLayoutCache().insertGroup(group, cells);
				graph.getGraphLayoutCache().collapse(new Object[]{group});

				graph.repaint((int) selection.getX(), (int) selection.getY(),
					(int) selection.getWidth(), (int) selection.getHeight());
				
				graph.setSelectionCell(group);
			}
		}
	}

	/**
	 * Ungroups a composition.
	 *
	 * @param graph The current {@link JGraph}.
	 */
	private void doUngroupComposition(JGraph graph) {
		Object[] cells = graph.getSelectionCells();

		GraphLayoutCache cache = graph.getGraphLayoutCache();
		cache.expand(cells);

		Object[] children = cache.ungroup(cells);
		setParentAttribute(null, children);

		graph.setSelectionCells(children);
	}

	/**
	 * Creates a region from the selected cells.
	 *
	 * @param graph The current {@link JGraph}.
	 */
	private void createRegion(JGraph graph) {
	    Object[] cells = graph.getSelectionCells();
	    if (cells.length > 0) {
	    	String title = JGraphEditorResources.getString(PROVIDE_REGION_NAME);

	    	ArchPadDiagram diagram = Utils.getPermanentFocusOwnerArchPadDiagram();
	    	RegionManager regionManager = diagram.getRegionManager();

	    	JGraphpadDialogs dialogs = JGraphpadDialogs.getSharedInstance();
	
	    	String name = null;
	    	do {
	    	    name = dialogs.valueDialog(title, REGION_DEFAULT_NAME);
	    	    if (name == null) {
		    	    return;
		    	}
	    	    if (regionManager.isRegionNameAcceptable(name)) {
	    	    	break;
	    	    } else {
	    	    	dialogs.errorDialog(graph,
	    	    		MESSAGE_UNACCEPTSABLE_REGION_NAME);
	    	    }
	    	} while (true);

	    	List<ElementGraphCell> eCells = new ArrayList<ElementGraphCell>();
	    	for (Object cell : cells) {
	    		if (cell instanceof ElementGraphCell) {
	    		    eCells.add((ElementGraphCell) cell);
	    		}
	    	}

	    	regionManager.createRegion(eCells.toArray(new ElementGraphCell[]{}),
	    		name);
	    }
	}

	/**
	 * Bundle of all actions in this class.
	 */
	public static class AllActions implements Bundle {

		private JGraphEditorAction[] mAllActions;

		private JGraphEditorAction mGroupAsSubArchitecture;

		private JGraphEditorAction mUngroupSubArchitecture;

		private JGraphEditorAction mCreateRegion;

		/**
		 * Constructs the action bundle for the specified editor.
		 *
		 * @param editor The enclosing editor for this bundle.
		 */
		public AllActions(JGraphEditor editor) {
			mGroupAsSubArchitecture = new ArchPadCellAction(
					NAME_COMPOSE_COMPOSITION);
			String key = ArchPadPlugin.KEY_COMPOSITION_PROTOTYPE;
			mGroupAsSubArchitecture.putValue(key,
					editor.getSettings().getObject(key));

			mUngroupSubArchitecture = new ArchPadCellAction(
					NAME_DECOMPOSE_COMPOSITION); 

			mCreateRegion = new ArchPadCellAction(NAME_CREATE_REGION);

			mAllActions = new JGraphEditorAction[3];
			mAllActions[0] = mGroupAsSubArchitecture;
			mAllActions[1] = mUngroupSubArchitecture;
			mAllActions[2] = mCreateRegion;
		}

		public JGraphEditorAction[] getActions() {
			return mAllActions;
		}
 
		public void update() {
			if (!Utils.isCurrentArchPadDiagram()) {
				mGroupAsSubArchitecture.setEnabled(false);
				mUngroupSubArchitecture.setEnabled(false);
				mCreateRegion.setEnabled(false);
				return;
			}

			JGraph graph = getPermanentFocusOwnerGraph();
			Object[] cells = graph.getSelectionCells();

			boolean createeRegionEnabled = cells.length > 0;
			mCreateRegion.setEnabled(createeRegionEnabled);

			boolean groupEnabled = cells.length > 1;
			mGroupAsSubArchitecture.setEnabled(groupEnabled);

			// assumption is made that there is one port per cell 
			boolean ungroupEnabled =
				graph.getDescendants(cells).length > cells.length * 2;
			mUngroupSubArchitecture.setEnabled(ungroupEnabled);	
		}
	}
}
